#include "priv.h"

#include "sockop.h"
#include "buffer.h"
#include "cli_log.h"

static struct event_base *ev_base = NULL;
static struct event ev_accept;

int sockop_ipv4_tostruct(char *addr_ptr, unsigned short port, struct sockaddr_in *sin)
{
    memset(sin, '\0', sizeof(struct sockaddr_in));
    sin->sin_family = AF_INET;
    sin->sin_port = htons(port);

    if (1 != inet_pton(AF_INET, addr_ptr, &sin->sin_addr))
    {
        return -1;
    }

    return 0;
}

int sockop_ipv6_tostruct(char *addr_ptr, unsigned short port, struct sockaddr_in6 *sin6)
{
    memset(sin6, '\0', sizeof(struct sockaddr_in6));
    sin6->sin6_family = AF_INET6;
    sin6->sin6_port = htons(port);

    if (1 != inet_pton(AF_INET6, addr_ptr, &sin6->sin6_addr))
    {
        return -1;
    }

    return 0;
}

int sockop_usock_tostruct(char *path, struct sockaddr_un *sun)
{
    memset (sun, 0, sizeof (struct sockaddr_un));
    sun->sun_family = AF_UNIX;

    snprintf(sun->sun_path, sizeof(sun->sun_path), "%s", path);

    return 0;
}

static sockop_conn_t *sockop_client_create(sockop_ctx_t *ctx, int sock)
{
    sockop_conn_t *client = calloc(1, sizeof(sockop_conn_t));
    if (!client)
    {
        return NULL;
    }

    client->input = calloc(1, SOCKOP_READ_BUFSIZ);
    if (!client->input)
    {
        free(client);
        return NULL;
    }
    client->output = bf_buffer_alloc(ctx->cache_size, 0);
    if (!client->output)
    {
        free(client->input);
        free(client);
        return NULL;
    }

    client->sock = sock;
    client->ctx = ctx;

    return client;
}

static void sockop_client_delete(sockop_conn_t *client)
{
    if (client)
    {
        if (client->input)
        {
            free(client->input);
        }
        if (client->output)
        {
            bf_buffer_free(client->output);
        }
        free(client);
    }
}

int sockop_out(void *out, const char *format, ...)
{
    va_list args;
    char buf[ARRARY_SIZE_1024];
    int size = ARRARY_SIZE_1024;
    int len = 0;
    char *p = NULL;

    va_start(args, format);

    len = vsnprintf(buf, sizeof(buf), format, args);

    if (len < 0 || len >= size)
    {
        while (1)
        {
            if (len > -1)
            {
                size = len + 1;
            }
            else
            {
                size = size * 2;
            }

            p = realloc(p, size);
            if (!p)
            {
                return -1;
            }

            len = vsnprintf(p, size, format, args);
            if (len > -1 && len < size)
            {
                break;
            }
        }
    }

    if (!p)
    {
        p = buf;
    }

    if (-1 == bf_buffer_write((bf_buffer_t *)out, (unsigned char *)p, len))
    {
        return -1;
    }

    if (p != buf)
    {
        free(p);
    }

    va_end(args);

    return len;
}

static void sockop_read(int fd, short int event, void *arg)
{
    (void)event;
    sockop_conn_t *client = arg;
    int nbytes;

    nbytes = read(fd, client->input, SOCKOP_READ_BUFSIZ);
    if (nbytes <= 0)
    {
        goto __end;
    }

    sockop_ctx_t *ctx = client->ctx;
    ctx->cb(client->input, nbytes, client->output, ctx->arg);
    unsigned zero = 0;
    bf_buffer_write(client->output, (unsigned char *)&zero, 4);

__end:

    event_del(&client->ev_read);
    if (bf_buffer_total_len(client->output) > 0)
    {
        event_add(&client->ev_write, NULL);
    }
    else
    {
        sockop_client_delete(client);
    }
}

static void sockop_flush(int fd, short int event, void *arg)
{
    (void)event;
    (void)fd;
    sockop_conn_t *client = arg;
    int wcnt = 0;

    bf_buffer_t *b_next = client->output;

    while (b_next != NULL)
    {
        wcnt = write(client->sock, bf_buffer_mtod(b_next, char *), bf_buffer_data_len(b_next));
        if (wcnt <= 0)
        {       
            if(errno != EINTR)
            {
                break;
            }
            continue;
        }

        b_next = b_next->next;
    }

    event_del(&client->ev_write);
    sockop_client_delete(client);
}
static void sockop_accept(int accept_sock, short int event, void *arg)
{
    (void)event;
    int sock;
    sockop_ctx_t *ctx = arg;

#if 0
    struct sockaddr *sa = NULL;
    socklen_t socklen;
    struct sockaddr_in sin; // ipv4地址信息
    struct sockaddr_in6 sin6; // ipv6地址信息
    struct sockaddr_un sun; // unix地址信息

    if (SOCK_TYPE_IPV4 == ctx->type)
    {
        memset (&sin, '\0', sizeof(struct sockaddr_in));
        sa = (struct sockaddr *)&sin;
    }
    else if (SOCK_TYPE_IPV6 == ctx->type)
    {
        memset (&sin6, '\0', sizeof(struct sockaddr_in6));
        sa = (struct sockaddr *)&sin6;
    }
    else if (SOCK_TYPE_UNIX == ctx->type)
    {
        memset (&sun, '\0', sizeof(struct sockaddr_un));
        sa = (struct sockaddr *)&sun;
    }

    sock = accept(accept_sock, sa, &socklen);
#else
    sock = accept(accept_sock, NULL, NULL);
#endif
    if (sock < 0)
    {
        CLI_ERRLOG("can't accept socket : %s", strerror(errno));
        return;
    }

    sockop_conn_t *client = sockop_client_create(ctx, sock);
    if (!client)
    {
        close(sock);
        return;
    }

    event_set(&client->ev_read, client->sock, EV_READ | EV_PERSIST, sockop_read, (void *)client);
    event_base_set(ev_base, &client->ev_read);
    event_set(&client->ev_write, client->sock, EV_WRITE | EV_PERSIST, sockop_flush, (void *)client);
    event_base_set(ev_base, &client->ev_write);
    event_add(&client->ev_read, NULL);

    return;
}

static int sockop_create(sockop_ctx_t *ctx)
{
    struct sockaddr *sa = NULL;
    int socklen;

    if (SOCK_TYPE_IPV4 == ctx->type)
    {
        sa = (struct sockaddr *)&ctx->sin;
        socklen = sizeof(struct sockaddr_in);
    }
    else if (SOCK_TYPE_IPV6 == ctx->type)
    {
        sa = (struct sockaddr *)&ctx->sin6;
        socklen = sizeof(struct sockaddr_in6);
    }
    else if (SOCK_TYPE_UNIX == ctx->type)
    {
        sa = (struct sockaddr *)&ctx->sun;
        socklen = sizeof(ctx->sun.sun_family) + strlen(ctx->sun.sun_path);
    }
    else
    {
        CLI_ERRLOG("invalid socket type\n");
        return -1; 
    }

    int domain = (ctx->type == SOCK_TYPE_UNIX) ? AF_UNIX : AF_INET; 
    int sock;

    // 创建一个 socket 用于 tcp 通信
    if ((sock = socket(domain, SOCK_STREAM, 0)) < 0)
    { 
        CLI_ERRLOG("can't create socket : %s", strerror(errno));
        return -1;
    }

    ctx->sa = sa;
    ctx->socklen = socklen;
    ctx->sock = sock;

    return 0;
}

int sockop_server_run(sockop_ctx_t *ctx, int log_init)
{
    if (log_init)
    {
        cli_log_init(7, "cmd_server");
    }

    if (sockop_create(ctx) < 0)
    {
        return -1;
    }

    if (bind(ctx->sock, ctx->sa, ctx->socklen) < 0)
    {
        CLI_ERRLOG("can't bind socket : %s", strerror(errno));
        close(ctx->sock); 
        ctx->sock = -1;
        return -1;
    }

    if (listen(ctx->sock, 20) < 0)
    {
        CLI_ERRLOG("can't listen socket : %s", strerror(errno));
        close (ctx->sock); 
        return -1;
    }
 
    ev_base = event_init();
    event_set(&ev_accept, ctx->sock, EV_READ | EV_PERSIST ,sockop_accept, ctx);
    event_base_set(ev_base, &ev_accept);
    event_add(&ev_accept, NULL);

    if (event_base_dispatch(ev_base) < 0)
    {
        close (ctx->sock); 
        CLI_ERRLOG("event_dispatch() failed\n");
        return -1;
    }

    event_base_free(ev_base);

    return 0;
}

void sockop_server_exit(void)
{
    //event_base_loopbreak(ev_base);
    if (ev_base)
    {
        event_base_loopexit(ev_base, NULL);
    }
}

int sockop_client_conn(sockop_ctx_t *ctx)
{
    if (sockop_create(ctx) < 0)
    {
        return -1;
    }

    if (connect(ctx->sock, ctx->sa, ctx->socklen) != 0)
    {
        close(ctx->sock);
        CLI_ERRLOG("Connect ");
        return -1;
    }

    CLI_INFOLOG("server connected\n");

    return 0;
}

int sockop_cmd_exec(int sock, char *line, FILE *fp)
{
    char buf[ARRARY_SIZE_8192];
    int nbytes;
    int ret;
    int i;

    ret = write(sock, line, strlen(line) + 1);
    if (ret <= 0)
    {
        return -1;
    }

    while (1)
    {
        //注意阻塞有时间下
        nbytes = read(sock, buf, sizeof(buf) - 1);
        if (nbytes <= 0 && errno != EINTR)
        {
            return 0;
        }

        if (nbytes > 0)
        {
            buf[nbytes] = '\0';
            fprintf(fp, "%s", buf);
            fflush(fp);
            if (nbytes >= 4)
            {
                i = nbytes - 4;
                if (buf[i] == '\0' && buf[i + 1] == '\0' && buf[i + 2] == '\0')
                {
                    ret = buf[i + 3];
                    break;
                }
            }
        }
    }

    return 0;
}

void sockop_client_exit(sockop_ctx_t *ctx)
{
    close(ctx->sock);
    ctx->sock = -1;
}
